using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.ComponentModel;
using System.Drawing;
using System.Configuration;
using System.Reflection;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Text;


using BizElements.BusinessLayer;

namespace BizElements.Web
{
	/// <summary>
	/// Editable DataGrid control with columns that may be bound to business class properties.
	/// </summary>
	[ToolboxData("<{0}:BoundDataGrid runat=server></{0}:BoundDataGrid>")]
	public class BoundDataGrid : System.Web.UI.WebControls.DataGrid
	{
        internal Unit MinItemHeight = new Unit("10px");
        static readonly int? DdlWidth = 200;
        static readonly int? TxtWidth = 200;

		#region GridRowSelectMode - public enum.

		/// <summary>
		/// Indicates row select mode and style.
		/// </summary>
		public enum GridRowSelectMode
		{
			/// <summary>
			/// One row maybe selected at a time.
			/// </summary>
			/// <remarks>Current row is automatically deselected when another row is selected.</remarks>
			SingleRow = 0,

			/// <summary>
			/// Multiple rows may be selected. Selection and deselection are performed by mouse keys only.
			/// </summary>
			/// <remarks>Row is selected when clicked and deselected when clicked again.
			/// Right click also selects a row, but it cannot be used to deselect it.</remarks>
			MultiRow = 1,

			/// <summary>
			/// Multiple rows may be selected. Uses standard Windows hotkeys for selection.
			/// </summary>
			/// <remarks>First row is selected by mouse. Additional rows are selected/deselected by holding CTRL key down
			/// and clicking. Right click may only be used to select the first row.</remarks>
			MultiRowWindowsStyle = 2
		}

		#endregion

		#region SelectedItemKey - nested type.

		// Used by HandleItemCreated to reduce parsing and to remove items that are no longer present in the data-source.
		private class SelectedItemKey
		{
			public string KeyValue;
			// Marks that an item with the selected key value exists in the data source.
			// Items that don't exist should be removed from the selected values list before rendering.
			public bool ItemExistsInDataSource;

			public SelectedItemKey(string keyValue)
			{
				this.KeyValue = keyValue;
				this.ItemExistsInDataSource = false;
			}
		}

		#endregion

		#region Constants.

		/// <summary>
		/// Delimites rows/records in hidden text field that selected item's attributes (key value, color...).
		/// </summary>
		const string RowDelimiter = ";";

		/// <summary>
		/// Delimites atributes of a row in hidden text field.
		/// </summary>
		const string RowAttributesDelimiter = ",";        

		#endregion

		#region Fields.

		// Contains attributes of selected rows.
		TextBox selectedValuesTxt = new TextBox();
		// Single or multi-row select mode.
		GridRowSelectMode rowSelectMode = GridRowSelectMode.SingleRow;
		// Formats messages, eg. error messages returned from bound business objects.
		IMessageFormatter formatter = new MessageFormatter();
		// Templates used to display and edit data.
        //TemplatedBoundColumnCollection boundTemplates = new TemplatedBoundColumnCollection();

		// Color of error label.
		Color errorColor = Color.Red;
		// Error label CSS class.
		string errorCss;
		// Errors are set in DisplayErrors method.
		string[] errors;

		// Created in item-bound event. After all items are bound it will mark which items exist in the
		// data-source and which have to be removed from the list of selected items.
		SelectedItemKey[] cachedSelectedValues;

		// ViewState of rows/items which contains whole datasource can be seperately disabled to reduce network traffic.
		bool enableItemViewState = true;
		// Client-side script invoked when left-click occurs.
        string leftClickAction = "";
		// Enables/disables right-clicks.
		bool rightClickEnabled = false;
		// Client-side script invoked when right-click occurs.
        string rightClickAction = "";
		// Invoked when edit item is focused and enter is pressed.
		string acceptEditActionScript = "";
		// Invoked when edit item is focused and escape is pressed.
		string cancelEditActionScript = "";
        // Enables/disables double-clicks.
        bool doubleClickEnabled = false;
        // Client-side script invoked when double-click occurs.
        string doubleClickAction = "";
        // Client-side script invoked when the selection changes.
        string onChangeAction = "";

        // Tooltips.
        string itemToolTipFormat;
        string itemToolTipFields;
        IDictionary<string, string> fieldToolTipFormats = new Dictionary<string, string>();
        IDictionary<string, string> fieldToolTipArguments = new Dictionary<string, string>();
        bool fieldToolTipsAutoGenerated;

		#endregion

		#region Constructors and Init.

		/// <summary>
		/// Initializes a new instance of the <see cref="BoundDataGrid"/> class.
		/// </summary>
		public BoundDataGrid()
		{
			this.AutoGenerateColumns = false;
			this.selectedValuesTxt.Style["display"] = "none";
			this.ItemCreated += new DataGridItemEventHandler(this.HandleItemCreated);
		}

        #endregion

        #region OnInit.

        /// <summary>
        /// Raises the <b>Init</b> event.
        /// </summary>
        /// <param name="e">An <b>EventArgs</b> object that contains the event data.</param>
        protected override void OnInit(EventArgs e)
        {            
            base.OnInit(e);
            Page.RegisterRequiresControlState(this);
            AssignIDsToChildControls();
            LoadPostBackData();
            if (!this.enableItemViewState)
                this.ItemCreated += new DataGridItemEventHandler(DisableViewStateForItem);

            if (!DesignMode)
                JScript.RegisterClientScriptResource(GetType(), "BizElements.Web.BoundDataGrid.js");
        }

		/// <summary>
		/// Assign unique IDs to child controls.
		/// </summary>
		private void AssignIDsToChildControls()
		{
            // selectedValuesTxt is not added to Control collection which would make ASP.NET to automatically
            // assign ID, UniqueID and ClientID. The mechanism kicks in to late, the ClientID is required as
            // soon as possible to render client-side script for each bound item.
            // The downside is that the selectedValuesTxt.Text property is not serialized to ViewState
            // meaning that it has to be retrieved manually in LoadPostBackData method.
            // In addition if grid's Visible property is set to false, the Text property must be manually
            // maintained in the control state.
            this.selectedValuesTxt.ID = this.ID + "__SelectedValues";
		}

		/// <summary>
		/// Loads postback data. Eg. data about rows client selected.
		/// </summary>
		private void LoadPostBackData()
		{
            if (!DesignMode)
            {
                string postedSelectedValues = this.Page.Request[this.selectedValuesTxt.ClientID];
                if (postedSelectedValues != null)
                    this.selectedValuesTxt.Text = postedSelectedValues;
            }
		}

		/// <summary>
		/// Disables data item's ViewState (datasource). Reduces network traffic.
		/// </summary>
		private void DisableViewStateForItem(object sender, DataGridItemEventArgs e)
		{
			if (e.Item.ItemType == ListItemType.Item  ||  e.Item.ItemType == ListItemType.AlternatingItem)
				e.Item.EnableViewState = false;
		}

		#endregion

        #region Control state.

        /// <summary>
        /// Saves any server control state changes that have occurred since the time the page was posted back to the server. 
        /// </summary>
        /// <returns>Returns the server control's current state. If there is no state associated with the control, this method returns a null reference (Nothing in Visual Basic).</returns>
        protected override object SaveControlState()
        {
            object baseClassContribution = base.SaveControlState();
            string selectedValuesContent = this.selectedValuesTxt.Text.Trim();
            Pair baseStateAndSelectedValues = new Pair(baseClassContribution, selectedValuesContent);
            return baseStateAndSelectedValues;
        }

        /// <summary>
        /// Restores control-state information from a previous page request that was saved by the <see cref="SaveControlState"/> method. 
        /// </summary>
        /// <param name="savedState">An <see cref="Object"/> that represents the control state to be restored.</param>
        protected override void LoadControlState(object savedState)
        {
            LoadPostBackData();
            Pair baseStateAndSelectedValues = savedState as Pair;
            if (baseStateAndSelectedValues != null)
            {
                base.LoadControlState(baseStateAndSelectedValues.First);
                if (!DesignMode)
                {
                    // Only if the POST variable doesn't exist and if it hasn't already been set/changed.
                    bool valuesAlreadySet = (this.selectedValuesTxt.Text.Length > 0);
                    if (!valuesAlreadySet)
                    {
                        bool valuesPostVariableExists = Page.Request[this.selectedValuesTxt.ClientID] != null;
                        if (!valuesPostVariableExists)
                            this.selectedValuesTxt.Text = Convert.ToString(baseStateAndSelectedValues.Second);
                    }
                }
            }
        }

        #endregion

        #region Properties.

        /// <summary>
		/// Gets or sets the JavaScript code that is executed when a user presses Enter key when editing an item.
		/// </summary>
		/// <remarks>Usually used to initiate a code-behind action that accepts the new values of currently edited item.</remarks>
		[Bindable(false), Category("Behavior"), DefaultValue(typeof(string), ""),
		Description("JavaScript code that is executed when a user presses Enter key when editing an item.")]
		public string AcceptEditAction
		{
			get {return this.acceptEditActionScript;}
			set {this.acceptEditActionScript = (value != null) ? value.Trim() : "";}
		}

		/// <summary>
		/// Gets or sets the JavaScript code that is executed when a user presses Escape key when editing an item.
		/// </summary>
		/// <remarks>Usually used to initiate a code-behind action that rejects the new values of currently edited item.</remarks>
		[Bindable(false), Category("Behavior"), DefaultValue(typeof(string), ""),
		Description("JavaScript code that is executed when a user presses Escape key when editing an item.")]
		public string CancelEditAction
		{
			get {return this.cancelEditActionScript;}
			set {this.cancelEditActionScript = (value != null) ? value.Trim() : "";}
		}

		/// <summary>
		/// Gets or sets the value which indicates whether the grid items will use ViewState. When set to false the network traffic is significantly reduced.
		/// </summary>
		[Bindable(false), Category("Behavior"), DefaultValue(typeof(bool), "true"), 
		Description("Indicates whether the grid items will use ViewState. When set to false the network traffic will be significantly reduced.")]
		public bool EnableItemViewState
		{
			get {return this.enableItemViewState;}
			set {this.enableItemViewState = value;}
		}

		/// <summary>
		/// Gets or sets the <see cref="IMessageFormatter"/> object used to format bound values.
		/// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public IMessageFormatter Formatter
		{
			get {return this.formatter;}
			set {this.formatter = value;}
		}

		/// <summary>
		/// Gets or sets the programmatic identifier assigned to the server control.
		/// </summary>
		[Bindable(false), Category("Misc"),
		Description("Programmatic identifier assigned to the server control.")]
		public override string ID
		{
			get
			{
				return base.ID;
			}
			set
			{
				base.ID = value;
                AssignIDsToChildControls();
			}
		}

        /// <summary>
        /// Gets or sets a value that indicates whether <see cref="System.Web.UI.WebControls.BoundColumn"/> objects are automatically created and displayed in the DataGrid control for each field in the data source.
        /// </summary>
        /// <value><b>true</b> if BoundColumn objects are automatically created and displayed; otherwise, <b>false</b>. The default value is <b>false</b>.</value>
        [DefaultValue(false)]
        public override bool AutoGenerateColumns
        {
            get { return base.AutoGenerateColumns; }
            set { base.AutoGenerateColumns = value; }
        }

		/// <summary>
		/// Gets or sets the JavaScript code that is executed when the grid is left-clicked.
		/// </summary>
		[Bindable(false), Category("Behavior"), DefaultValue(typeof(string), ""), 
		Description("JavaScript code that is executed when the grid is left-clicked.")]
		public string LeftClickAction
		{
            get { return this.leftClickAction; }
            set { this.leftClickAction = (value != null) ? value.Trim() : ""; }
		}

		/// <summary>
		/// Gets or sets the value which indicates row select mode and style.
		/// </summary>
		/// <remarks>Multi-row select doesn't work properly if paging is enabled.</remarks>
		[Bindable(false), Category("Behavior"), DefaultValue(typeof(GridRowSelectMode), "SingleRow"),
		Description("Indicates row select mode and style.")]
		public GridRowSelectMode RowSelectMode
		{
			get {return this.rowSelectMode;}
			set {this.rowSelectMode = value;}
		}

		/// <summary>
		/// Gets or sets the JavaScript code that is executed when the grid is right-clicked.
		/// </summary>
		/// <remarks>Often used to open a context menu.</remarks>
		[Bindable(false), Category("Behavior"), DefaultValue(typeof(string), ""), 
		Description("JavaScript code that is executed when the grid is right-clicked.")]
		public string RightClickAction
		{
            get { return this.rightClickAction; }
            set { this.rightClickAction = (value != null) ? value.Trim() : ""; }
		}

		/// <summary>
		/// Gets or sets the value which indicates whether the grid accepts right clicks. 
		/// If enabled, right click will select an item and execute the specified JavaScript code.
		/// </summary>
		[Bindable(false), Category("Behavior"), DefaultValue(typeof(bool), "false"), 
		Description("Indicates whether the grid accepts right clicks. Right click will select an item and execute the specified JavaScript code.")]
		public bool RightClickEnabled
		{
			get {return this.rightClickEnabled;}
			set {this.rightClickEnabled = value;}
		}

        /// <summary>
        /// Gets or sets the JavaScript code that is executed when the grid is double-clicked.
        /// </summary>
        [Bindable(false), Category("Behavior"), DefaultValue(typeof(string), ""),
        Description("JavaScript code that is executed when the grid is double-clicked.")]
        public string DoubleClickAction
        {
            get { return this.doubleClickAction; }
            set { this.doubleClickAction = (value != null) ? value.Trim() : ""; }
        }

        /// <summary>
        /// Gets or sets the value which indicates whether the grid accepts double clicks. 
        /// If enabled, double click will select an item and execute the specified JavaScript code.
        /// </summary>
        [Bindable(false), Category("Behavior"), DefaultValue(typeof(bool), "false"),
        Description("Indicates whether the grid accepts double clicks. Double click will select an item and execute the specified JavaScript code.")]
        public bool DoubleClickEnabled
        {
            get { return this.doubleClickEnabled; }
            set { this.doubleClickEnabled = value; }
        }		

        /// <summary>
        /// Gets or sets the JavaScript code that is executed when the selection changes.
        /// </summary>
        [Category("Behavior")]
        [DefaultValue(typeof(string), "")]
        [Description("JavaScript code that is executed when the selection changes.")]
        public string OnChangeAction
        {
            get { return this.onChangeAction; }
            set { this.onChangeAction = value; }
        }

        bool wrapBoundFields;

        /// <summary>
        /// Gets or sets whether the fields bound via <b>BindField</b> methods are wrapped.
        /// </summary>
        /// <value><b>true</b> to wrap cell contents; <b>false</b> otherwise. Default is <b>false</b>.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Indicates whether the fields bound via BindField methods are wrapped.")]
        public bool WrapBoundFields
        {
            get { return this.wrapBoundFields; }
            set { this.wrapBoundFields = value; }
        }

		#endregion

        #region Hotkeys.

        static class KeyCode
        {
            public const int Enter = 13;
            public const int Esc = 27;
            public const int LeftArrow = 37;
            public const int UpArrow = 38;
            public const int RightArrow = 39;
            public const int DownArrow = 40;
            public const int Delete = 46;
        }

        int moveLeftKeyCode = KeyCode.LeftArrow;

        /// <summary>
        /// Gets or sets the key which focuses the control left of the current control.
        /// </summary>
        /// <value>HTML key code. Default is <b>37</b>, ie. <b>left arrow</b> key.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(KeyCode.LeftArrow)]
        [Description("Key which focuses the control left of the current control.")]
        public int MoveLeftKeyCode
        {
            get { return this.moveLeftKeyCode; }
            set { this.moveLeftKeyCode = value; }
        }

        bool moveLeftKeyEnabled;

        /// <summary>
        /// Gets or sets whether the hotkey which focuses the control left of the current control is enabled.
        /// </summary>
        /// <value><b>true</b> to enable the hotkey; otherwise <b>false</b>. Default is <b>false</b>.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Indicates whether the hotkey which focuses the control left of the current control is enabled.")]
        public bool MoveLeftKeyEnabled
        {
            get { return this.moveLeftKeyEnabled; }
            set { this.moveLeftKeyEnabled = value; }
        }

        int moveUpKeyCode = KeyCode.UpArrow;

        /// <summary>
        /// Gets or sets the key which focuses the control above the current control.
        /// </summary>
        /// <value>HTML key code. Default is <b>38</b>, ie. <b>up arrow</b> key.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(KeyCode.UpArrow)]
        [Description("Key which focuses the control above the current control.")]
        public int MoveUpKeyCode
        {
            get { return this.moveUpKeyCode; }
            set { this.moveUpKeyCode = value; }
        }

        bool moveUpKeyEnabled;

        /// <summary>
        /// Gets or sets whether the hotkey which focuses the control above the current control is enabled.
        /// </summary>
        /// <value><b>true</b> to enable the hotkey; otherwise <b>false</b>. Default is <b>false</b>.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Indicates whether the hotkey which focuses the control above the current control is enabled.")]
        public bool MoveUpKeyEnabled
        {
            get { return this.moveUpKeyEnabled; }
            set { this.moveUpKeyEnabled = value; }
        }

        int moveRightKeyCode = KeyCode.RightArrow;

        /// <summary>
        /// Gets or sets the key which focuses the control right of the current control.
        /// </summary>
        /// <value>HTML key code. Default is <b>39</b>, ie. <b>right arrow</b> key.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(KeyCode.RightArrow)]
        [Description("Key which focuses the control right of the current control.")]
        public int MoveRightKeyCode
        {
            get { return this.moveRightKeyCode; }
            set { this.moveRightKeyCode = value; }
        }

        bool moveRightKeyEnabled;

        /// <summary>
        /// Gets or sets whether the hotkey which focuses the control right of the current control is enabled.
        /// </summary>
        /// <value><b>true</b> to enable the hotkey; otherwise <b>false</b>. Default is <b>false</b>.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Indicates whether the hotkey which focuses the control right of the current control is enabled.")]
        public bool MoveRightKeyEnabled
        {
            get { return this.moveRightKeyEnabled; }
            set { this.moveRightKeyEnabled = value; }
        }

        int moveDownKeyCode = KeyCode.DownArrow;

        /// <summary>
        /// Gets or sets the key which focuses the control below the current control.
        /// </summary>
        /// <value>HTML key code. Default is <b>40</b>, ie. <b>down arrow</b> key.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(KeyCode.DownArrow)]
        [Description("Key which focuses the control below the current control.")]
        public int MoveDownKeyCode
        {
            get { return this.moveDownKeyCode; }
            set { this.moveDownKeyCode = value; }
        }

        bool moveDownKeyEnabled;

        /// <summary>
        /// Gets or sets whether the hotkey which focuses the control below the current control is enabled.
        /// </summary>
        /// <value><b>true</b> to enable the hotkey; otherwise <b>false</b>. Default is <b>false</b>.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Indicates whether the hotkey which focuses the control below the current control is enabled.")]
        public bool MoveDownKeyEnabled
        {
            get { return this.moveDownKeyEnabled; }
            set { this.moveDownKeyEnabled = value; }
        }

        int previousItemKeyCode = KeyCode.UpArrow;

        /// <summary>
        /// Gets or sets the key which selects and hilights the previous item/row.
        /// </summary>
        /// <value>HTML key code. Default is <b>38</b>, ie. <b>up arrow</b> key.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(KeyCode.UpArrow)]
        [Description("Key which selects and hilights the previous item/row.")]
        public int PreviousItemKeyCode
        {
            get { return this.previousItemKeyCode; }
            set { this.previousItemKeyCode = value; }
        }

        bool previousItemKeyEnabled;

        /// <summary>
        /// Gets or sets whether the hotkey which selects and hilights the previous item/row is enabled.
        /// </summary>
        /// <value><b>true</b> to enable the hotkey; otherwise <b>false</b>. Default is <b>false</b>.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Indicates whether the hotkey which selects and hilights the previous item/row is enabled.")]
        public bool PreviousItemKeyEnabled
        {
            get { return this.previousItemKeyEnabled; }
            set { this.previousItemKeyEnabled = value; }
        }

        int nextItemKeyCode = KeyCode.DownArrow;

        /// <summary>
        /// Gets or sets the key which selects and hilights the next item/row.
        /// </summary>
        /// <value>HTML key code. Default is <b>40</b>, ie. <b>down arrow</b> key.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(KeyCode.DownArrow)]
        [Description("Key which selects and hilights the next item/row.")]
        public int NextItemKeyCode
        {
            get { return this.nextItemKeyCode; }
            set { this.nextItemKeyCode = value; }
        }

        bool nextItemKeyEnabled;

        /// <summary>
        /// Gets or sets whether the hotkey which selects and hilights the next item/row is enabled.
        /// </summary>
        /// <value><b>true</b> to enable the hotkey; otherwise <b>false</b>. Default is <b>false</b>.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Indicates whether the hotkey which selects and hilights the next item/row is enabled.")]
        public bool NextItemKeyEnabled
        {
            get { return this.nextItemKeyEnabled; }
            set { this.nextItemKeyEnabled = value; }
        }

        int defaultActionKeyCode = KeyCode.Enter;

        /// <summary>
        /// Gets or sets the key which executes the default action specified in <see cref="DoubleClickAction"/> property.
        /// </summary>
        /// <value>HTML key code. Default is <b>13</b>, ie. <b>enter</b> key.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(KeyCode.Enter)]
        [Description("Key which executes the default action.")]
        public int DefaultActionKeyCode
        {
            get { return this.defaultActionKeyCode; }
            set { this.defaultActionKeyCode = value; }
        }

        bool defaultActionKeyEnabled;

        /// <summary>
        /// Gets or sets whether the key which executes the default action specified in <see cref="DoubleClickAction"/> property is enabled.
        /// </summary>
        /// <value><b>true</b> to enable the hotkey; otherwise <b>false</b>. Default is <b>false</b>.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Indicates whether the hotkey which executes the default action is enabled.")]
        public bool DefaultActionKeyEnabled
        {
            get { return this.defaultActionKeyEnabled; }
            set { this.defaultActionKeyEnabled = value; }
        }

        int deleteActionKeyCode = KeyCode.Delete;

        /// <summary>
        /// Gets or sets the key which executes the action specified in <see cref="DeleteAction"/> property.
        /// </summary>
        /// <value>HTML key code. Default is <b>46</b>, ie. <b>delete</b> key.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(KeyCode.Delete)]
        [Description("Key which executes the delete action.")]
        public int DeleteActionKeyCode
        {
            get { return this.deleteActionKeyCode; }
            set { this.deleteActionKeyCode = value; }
        }

        bool deleteKeyEnabled;

        /// <summary>
        /// Gets or sets whether the key which executes the action specified in <see cref="DeleteAction"/> property is enabled.
        /// </summary>
        /// <value><b>true</b> to enable the hotkey; otherwise <b>false</b>. Default is <b>false</b>.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Indicates whether the hotkey which executes the delete action is enabled.")]
        public bool DeleteKeyEnabled
        {
            get { return this.deleteKeyEnabled; }
            set { this.deleteKeyEnabled = value; }
        }

        string deleteAction = "";

        /// <summary>
        /// Gets or sets the JavaScript code that is executed when the delete hotkey is pressed.
        /// </summary>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(""),
        Description("JavaScript code that is executed when the delete hotkey is pressed.")]
        public string DeleteAction
        {
            get { return this.deleteAction; }
            set { this.deleteAction = (value != null) ? value.Trim() : ""; }
        }

        private bool UsesHotKeys()
        {
            if (!JScript.IsInternetExplorer())
                return false;
            
            bool anyKeyEnabled = (this.PreviousItemKeyEnabled || this.NextItemKeyEnabled || this.DefaultActionKeyEnabled || this.DeleteKeyEnabled
                || this.MoveLeftKeyEnabled || this.MoveUpKeyEnabled || this.MoveRightKeyEnabled || this.MoveDownKeyEnabled);
            return anyKeyEnabled;
        }

        private string CallHotKeyEventHandler()
        {
            StringBuilder js = new StringBuilder();
            js.Append("Biz_BoundDataGrid_OnHotKey(event, ");
            js.Append(GetHotKeyCode(this.PreviousItemKeyEnabled, this.PreviousItemKeyCode));
            js.Append(", ");
            js.Append(GetHotKeyCode(this.NextItemKeyEnabled, this.NextItemKeyCode));
            js.Append(", ");

            js.Append(GetHotKeyCode(this.DefaultActionKeyEnabled, this.DefaultActionKeyCode));
            js.Append(", ");
            string jsDefaultAction;
            if (this.DefaultActionKeyEnabled && !string.IsNullOrEmpty(this.DoubleClickAction))
                jsDefaultAction = "'" + this.DoubleClickAction + "'";
            else
                jsDefaultAction = "null";
            js.Append(jsDefaultAction);

            js.Append(", ");
            js.Append(GetHotKeyCode(this.MoveLeftKeyEnabled, this.MoveLeftKeyCode));
            js.Append(", ");
            js.Append(GetHotKeyCode(this.MoveUpKeyEnabled, this.MoveUpKeyCode));
            js.Append(", ");
            js.Append(GetHotKeyCode(this.MoveRightKeyEnabled, this.MoveRightKeyCode));
            js.Append(", ");
            js.Append(GetHotKeyCode(this.MoveDownKeyEnabled, this.MoveDownKeyCode));
            js.Append(", ");
            
            js.Append(GetHotKeyCode(this.DeleteKeyEnabled, this.DeleteActionKeyCode));
            js.Append(", ");
            string jsDeleteAction;
            if (this.DeleteKeyEnabled && !string.IsNullOrEmpty(this.DeleteAction))
                jsDeleteAction = "'" + this.DeleteAction + "'";
            else
                jsDeleteAction = "null";
            js.Append(jsDeleteAction);

            js.Append(")");
            return js.ToString();
        }

        private static string GetHotKeyCode(bool isEnabled, int codeIfEnabled)
        {
            if (isEnabled)
                return codeIfEnabled.ToString();
            else
                return "null";
        }

        #endregion

        #region Selected values, indexes...

        /// <summary>
        /// Gets the selected key values. There may be more than one value if grid is in multi-row select mode.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public string[] SelectedValues
        {
            get
            {
                string selectedRowsTxt = this.selectedValuesTxt.Text.Trim();
                if (selectedRowsTxt.Length == 0)
                    return new string[0];

                // Remove last row delimiter (";").				
                if (selectedRowsTxt.EndsWith(RowDelimiter))
                    selectedRowsTxt = selectedRowsTxt.Substring(0, selectedRowsTxt.Length - 1);

                string[] rowData = selectedRowsTxt.Split(RowDelimiter.ToCharArray());
                string[] ids = new string[rowData.Length];

                for (int rowIdx = 0; rowIdx < ids.Length; rowIdx++)
                    // ID is the first attribute.
                    ids[rowIdx] = rowData[rowIdx].Substring(0, rowData[rowIdx].IndexOf(RowAttributesDelimiter));

                return ids;
            }
        }

        /// <summary>
        /// Gets or sets the index of the first selected item.
        /// </summary>
        public override int SelectedIndex
        {
            get
            {
                if (this.DataKeyField == null)
                {
                    return base.SelectedIndex;
                }
                else
                {
                    int[] allItemIndexes = this.SelectedIndexes;
                    return (allItemIndexes.Length > 0) ? allItemIndexes[0] : -1;
                }
            }
            set
            {
                if (this.DataKeyField == null)
                {
                    base.SelectedIndex = value;
                }
                else
                {
                    if (value < 0)
                        ClearSelectedValues();
                    else
                        this.SelectedIndexes = new int[] { value };
                }
            }
        }

        /// <summary>
        /// Gets or sets indexes of all selected items.
        /// </summary>
        /// <exception cref="InvalidOperationException">Generated if DataKeyField is not defined.</exception>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual int[] SelectedIndexes
        {
            get
            {
                if (this.DataKeyField == null)
                    throw new InvalidOperationException("DataKeyField must be defined to get SelectedIndexes.");

                StringCollection selectedKeys = new StringCollection();
                selectedKeys.AddRange(this.SelectedValues);
                List<int> indexes = new List<int>(selectedKeys.Count);
                for (int idx = 0; idx < this.DataKeys.Count; idx++)
                {
                    string key = Convert.ToString(this.DataKeys[idx]);
                    bool isSelected = !string.IsNullOrEmpty(key) && selectedKeys.Contains(key);
                    if (isSelected)
                        indexes.Add(idx);
                }

                return indexes.ToArray();
            }
            set
            {
                if (this.DataKeyField == null)
                    throw new InvalidOperationException("DataKeyField must be defined to set SelectedIndexes.");

                StringBuilder selectedRowsTxt = new StringBuilder();
                foreach (int idx in value)
                {
                    string key = this.DataKeys[idx].ToString();
                    bool isAlternatingItem = (idx % 2 == 1);
                    TableItemStyle itemStyle = (isAlternatingItem) ? this.AlternatingItemStyle : this.ItemStyle;
                    string rowColor = itemStyle.GetStyleAttributes(this)["background-color"];
                    if (rowColor == null)
                        rowColor = "";

                    selectedRowsTxt.Append(key);
                    selectedRowsTxt.Append(RowAttributesDelimiter);
                    selectedRowsTxt.Append(rowColor);
                    selectedRowsTxt.Append(RowDelimiter);
                }

                this.selectedValuesTxt.Text = selectedRowsTxt.ToString();
                RecreateItems();
            }
        }

        /// <summary>
        /// Gets the name of the generated client-side script which returns the key values of currently selected row.
        /// </summary>
        /// <remarks>Always use this property to find out the name of the script. Do not hard-code script name 
        /// such as MyGrid__GetVal because the algorithm that creates the script name may change.</remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Obsolete("Avoid using \"GetSelectedKeyValueJScript\". It is not fully compatibile with AJAX. The script with that name is not available when a grid is dynamically added to the page during an asynchronous postback.")]
        public string GetSelectedKeyValueJScript
        {
            get { return CreateGetSelectedKeyValueJScriptName(); }
        }

        private string CreateGetSelectedKeyValueJScriptName()
        {
            return this.ID + "__GetVal";
        }

        /// <summary>
		/// Deselects all items/rows.
		/// </summary>
		public void ClearSelectedValues()
		{
			this.selectedValuesTxt.Text = "";
            RecreateItems();
        }

        #endregion

        #region DataBind, ReBind.

        bool itemsCreated;

        /// <summary>
        /// Binds the control and all its child controls to the specified data source. 
        /// </summary>
        public override void DataBind()
        {
            if (this.FieldToolTipsAutoGenerated)
                AutoGenerateFieldToolTips();

            base.DataBind();
            this.itemsCreated = true;
        }

        /// <summary>
        /// Binds a data source to the invoked server control and all its child controls with an option to raise the DataBinding event.
        /// </summary>
        /// <param name="raiseOnDataBinding">true if the DataBinding event is raised; otherwise, false. The default is false.</param>
        protected override void DataBind(bool raiseOnDataBinding)
        {
            base.DataBind(raiseOnDataBinding);
            this.itemsCreated = true;
        }

        /// <summary>
        /// Binds a data source if the grid items have already been created/bound.
        /// Does nothing if DataBind method hasn't been invoked yet.
        /// </summary>
        private void RecreateItems()
        {
            if (this.itemsCreated)
            {
                this.cachedSelectedValues = null;
                DataBind();
            }
        }

        #endregion

        bool alwaysEditBoundFields;

        /// <summary>
        /// Gets or sets whether, for the fields bound via <b>BindField</b> methods, by default the edit template will be used as item template, too.
        /// </summary>
        /// <value><b>true</b> to used edit templates by default; <b>false</b> otherwise. Default is <b>false</b>.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Indicates whether the edit template will be used as item template, too.")]
        public bool AlwaysEditBoundFields
        {
            get { return this.alwaysEditBoundFields; }
            set { this.alwaysEditBoundFields = value; }
        }

        #region FieldBinding - bindable fields, templates, populating BusinessObject.

        /// <summary>
		/// Creates a column with controls bound to the given business class property.
		/// </summary>
		/// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
        /// <returns>Index of the new grid column to which the field is bound.</returns>
		public int BindField(IBindableField field)
		{
			return BindField(field, this.AlwaysEditBoundFields);
		}

		/// <summary>
		/// Creates a column with controls bound to the given business class property.
		/// </summary>
		/// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
        /// <param name="columnWidth">The width of the column bound to the field</param>
        /// <returns>Index of the new grid column to which the field is bound.</returns>
		public int BindField(IBindableField field, int columnWidth)
		{
			BoundTemplateBase editTemplate = GetDefaultEditTemplate(field);
            editTemplate.Width = columnWidth;
            BoundTemplateBase itemTemplate = (this.AlwaysEditBoundFields) ? editTemplate : GetDefaultItemTemplate(field);
            itemTemplate.Width = columnWidth;
            return BindField(field, editTemplate, itemTemplate);
		}

		/// <summary>
		/// Creates a column with controls bound to the given business class property.
		/// </summary>
		/// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
        /// <param name="columnWidth">The width of the column bound to the field</param>
		/// <param name="editTemplateCssClass">The CSS Class name applied to the control which is used when editing the field.</param>
        /// <returns>Index of the new grid column to which the field is bound.</returns>
        public int BindField(IBindableField field, int columnWidth, string editTemplateCssClass)
		{
			BoundTemplateBase editTemplate = GetDefaultEditTemplate(field);
            editTemplate.Width = columnWidth;
			editTemplate.CssClass = editTemplateCssClass;
            BoundTemplateBase itemTemplate = (this.AlwaysEditBoundFields) ? editTemplate : GetDefaultItemTemplate(field);
            itemTemplate.Width = columnWidth;
            return BindField(field, editTemplate, itemTemplate);
		}

        /// <summary>
		/// Creates a column with controls bound to the given business class property.
		/// </summary>
		/// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
		/// <param name="alwaysUseEditTemplate">Indicates whether the edit template will be used as item template, too.</param>
        /// <returns>Index of the new grid column to which the field is bound.</returns>
        public int BindField(IBindableField field, bool alwaysUseEditTemplate)
        {
            BoundTemplateBase editTemplate = GetDefaultEditTemplate(field);
            if (alwaysUseEditTemplate)
                return BindField(field, editTemplate, editTemplate);
            else
                return BindField(field, editTemplate, null);
        }

		/// <summary>
		/// Creates a column with controls bound to the given business class property.
		/// </summary>
		/// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
		/// <param name="editTemplate">Template used when editing the field.</param>
        /// <returns>Index of the new grid column to which the field is bound.</returns>
		public int BindField(IBindableField field, BoundTemplateBase editTemplate)
		{
			return BindField(field, editTemplate, this.AlwaysEditBoundFields);
		}

		/// <summary>
		/// Creates a column with controls bound to the given business class property.
		/// </summary>
		/// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
		/// <param name="editTemplate">Template used when editing the field.</param>
		/// <param name="alwaysUseEditTemplate">Indicates whether the edit template will be used as item template, too.</param>
        /// <returns>Index of the new grid column to which the field is bound.</returns>
		public int BindField(IBindableField field, BoundTemplateBase editTemplate, bool alwaysUseEditTemplate)
		{
            if (editTemplate == null)
                editTemplate = GetDefaultEditTemplate(field);

			if (alwaysUseEditTemplate)
				return BindField(field, editTemplate, editTemplate);
			else
				return BindField(field, editTemplate, null);
		}

		/// <summary>
		/// Creates a column with controls bound to the given business class property.
		/// </summary>
		/// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
		/// <param name="editTemplate">Template used when editing the field. If NULL then the default template is used.</param>
		/// <param name="itemTemplate">Template used to display the field. If NULL then the default template is used.</param>
        /// <returns>Index of the new grid column to which the field is bound.</returns>
		public int BindField(IBindableField field, BoundTemplateBase editTemplate, BoundTemplateBase itemTemplate)
		{	
			TemplateColumn col = new TemplateColumn();
			
			if (editTemplate == null)
				editTemplate = GetDefaultEditTemplate(field);
			if (editTemplate.BoundField == null)
				editTemplate.BoundField = field;
			if (editTemplate.Formatter == null)
				editTemplate.Formatter = this.Formatter;
			col.EditItemTemplate = editTemplate;

			if (itemTemplate == null)
				itemTemplate = GetDefaultItemTemplate(field);
			if (itemTemplate.BoundField == null)
                itemTemplate.BoundField = field;
			if (itemTemplate.Formatter == null)
				itemTemplate.Formatter = this.Formatter;
			col.ItemTemplate = itemTemplate;
			
			col.SortExpression = field.BindingName;
			col.HeaderText = field.Caption;
			col.ItemStyle.Wrap = this.WrapBoundFields;
			this.Columns.Add(col);
            int newColIdx = this.Columns.Count - 1;
            if (itemTemplate.Width != default(Unit))
                this.Columns[newColIdx].ItemStyle.Width = itemTemplate.Width;

            return newColIdx;
		}

		/// <summary>
		/// Creates a column with controls bound to the given business class property.
		/// </summary>
		/// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
		/// <param name="editTemplate">Template used when editing the field. If NULL then the default template is used.</param>
		/// <param name="itemTemplate">Template used to display the field. If NULL then the default template is used.</param>
		/// <param name="displayField">Specifies which field will be displayed in item template. 
		/// Ignored if NULL, if no satellite fields are given or if the specified field doesn't exist.</param>
        /// <returns>Index of the new grid column to which the field is bound.</returns>
		public int BindField(IBindableField field, BoundTemplateBase editTemplate, BoundTemplateBase itemTemplate, string displayField)
		{
			bool useSatelliteField = false;
			if (displayField != null  &&  field.SatelliteFields != null  &&  field.SatelliteFields.Length > 0)
			{
				foreach (string satelliteField in field.SatelliteFields)
				{
					if (satelliteField == displayField)
					{
						useSatelliteField = true;
						break;
					}
				}
			}

			TemplateColumn col = new TemplateColumn();
			
			if (editTemplate == null)
				editTemplate = GetDefaultEditTemplate(field);
			if (editTemplate.BoundField == null)
				editTemplate.BoundField = field;
			if (editTemplate.Formatter == null)
				editTemplate.Formatter = this.Formatter;
			col.EditItemTemplate = editTemplate;

			IBindableField itemField;
			if (useSatelliteField)
				itemField = new BindableField(displayField, typeof(object));
			else
				itemField = field;

			if (itemTemplate == null)
				itemTemplate = GetDefaultItemTemplate(itemField);
			if (itemTemplate.BoundField == null)
                itemTemplate.BoundField = itemField;
			if (itemTemplate.Formatter == null)
				itemTemplate.Formatter = this.Formatter;
			col.ItemTemplate = itemTemplate;
			
			col.SortExpression = field.BindingName;
			col.HeaderText = field.Caption;
			col.ItemStyle.Wrap = this.WrapBoundFields;
			this.Columns.Add(col);
            return this.Columns.Count - 1;
		}

		/// <summary>
		/// Creates a default item/display template for the given field.
		/// </summary>
		private BoundTemplateBase GetDefaultItemTemplate(IBindableField field)
		{
			BoundTemplateBase itemTemplate = new BoundLabelTemplate(field, this.Formatter);
			itemTemplate.AccessMode = FieldAccessMode.ReadOnly;
			return itemTemplate;
		}

		/// <summary>
		/// Creates a default edit template for the given field.
		/// </summary>
		private BoundTemplateBase GetDefaultEditTemplate(IBindableField field)
		{
			BoundTemplateBase editTemplate;

			if (field.CodeList != null)
			{
                editTemplate = new BoundDropDownListTemplate(field, this.Formatter);
                if (DdlWidth.HasValue)
                    editTemplate.Width = DdlWidth.Value;
			}
            else if (TypeUtil.IsDate(field.DataType))
            {
                editTemplate = new BoundCalendarTemplate(field, this.Formatter);
            }
            else
            {
                editTemplate = new BoundTextBoxTemplate(field, this.Formatter);
                if (TxtWidth.HasValue)
                    editTemplate.Width = TxtWidth.Value;
            }

			return editTemplate;
		}

        /// <summary>
		/// Populates a business object with the data stored in the item at the specified index.
		/// </summary>
		/// <param name="itemIndex">Index of the item/row whose values are copied to a business object.</param>
        /// <param name="bizObject"><see cref="IValueObject"/> to be populated.</param>
		/// <returns>Collection of errors encountered while parsing the values and populating the business object.
		/// Empty collection if error have been encountered.</returns>
        [Obsolete("Prefer using the following method overload: PopulateBusinessObject(int itemIndex, IValueObject bizObject, string defaultParseError, string integerParseError, string numberParseError, string datetimeParseError)")]
        public BusinessRuleCollection PopulateBusinessObject(int itemIndex, IValueObject bizObject)
        {
            return PopulateBusinessObject(itemIndex, bizObject, null, null, null, null);
        }

		/// <summary>
		/// Populates a business object with the data stored in the item at the specified index.
		/// </summary>
		/// <param name="itemIndex">Index of the item/row whose values are copied to a business object.</param>
        /// <param name="bizObject"><see cref="IValueObject"/> to be populated.</param>
        /// <param name="defaultParseError">Message displayed when a parsing error occures.</param>
        /// <param name="integerParseError">Message displayed when parsing of integer value fails.</param>
        /// <param name="numberParseError">Message displayed when number parsing fails.</param>
        /// <param name="datetimeParseError">Message displayed when parsing of DateTime value fails.</param>
		/// <returns>Collection of errors encountered while parsing the values and populating the business object.
		/// Empty collection if error have been encountered.</returns>
        public BusinessRuleCollection PopulateBusinessObject(int itemIndex, IValueObject bizObject, 
            string defaultParseError, string integerParseError, string numberParseError, string datetimeParseError)
		{
			if (itemIndex < 0  ||  itemIndex >= this.Items.Count)
				return new BusinessRuleCollection();

			bool useEditTemplate = (itemIndex == this.EditItemIndex);
			DataGridItem gridItem = this.Items[itemIndex];

			// Create bindings between business object's fields and IBoundControl controls created by bound templates.
            BusinessObjectBindingContext bindings = new BusinessObjectBindingContext(this.Formatter)
            {
                DefaultParseError = defaultParseError,
                IntegerParseError = integerParseError,
                NumberParseError = numberParseError,
                DatetimeParseError = datetimeParseError
            };

			for (int cellIdx=0; cellIdx<this.Columns.Count; cellIdx++)
			{	
				TemplateColumn templateCol = this.Columns[cellIdx] as TemplateColumn;
				if (templateCol != null)
				{
					BoundTemplateBase boundTemplate;
					if (useEditTemplate)
						boundTemplate = templateCol.EditItemTemplate as BoundTemplateBase;
					else
						boundTemplate = templateCol.ItemTemplate as BoundTemplateBase;

					if (boundTemplate != null)
					{
						IBoundControl boundControl = gridItem.Cells[cellIdx].Controls[0] as IBoundControl;
						if (boundControl != null)
							bindings.BindField(boundTemplate.BoundField, boundControl);
					}
				}
			}

			// Populate business object and return parse errors.
			// Note: if error occures, business object will contain partial/invalid data.
			BusinessRuleCollection errors = bindings.PopulateBusinessObject(bizObject, false);
			return errors;		
		}


        /// <summary>
        /// Gets the column bound to the specified field.
        /// </summary>
        /// <param name="bindingName">Data field to which a DataGrid column is bound to.</param>
        /// <returns>DataGridColumn or null if no matching columns is found.</returns>
        public virtual DataGridColumn GetColumn(string bindingName)
        {
            int colIdx = GetColumnIndex(this, bindingName, false);
            return (colIdx >= 0) ? this.Columns[colIdx] : null;
        }

        /// <summary>
        /// Gets the column bound to the specified field.
        /// </summary>
        /// <param name="bindingName">Data field to which a DataGrid column is bound to.</param>
        /// <param name="ignoreCase">Indicates whether case-sensitive or insensitive search is performed.</param>
        /// <returns>DataGridColumn or null if no matching column is found.</returns>
        public virtual DataGridColumn GetColumn(string bindingName, bool ignoreCase)
        {
            int colIdx = GetColumnIndex(this, bindingName, ignoreCase);
            return (colIdx >= 0) ? this.Columns[colIdx] : null;
        }

        /// <summary>
        /// Gets the ItemTemplate of the column bound to the specified field.
        /// </summary>
        /// <param name="bindingName">Data field to which a DataGrid column is bound to.</param>
        /// <returns>ITemplate or null if no matching template column is found.</returns>
        public virtual ITemplate GetItemTemplate(string bindingName)
        {
            return GetItemTemplate(bindingName, false);
        }

        /// <summary>
        /// Gets the ItemTemplate of the column bound to the specified field.
        /// </summary>
        /// <param name="bindingName">Data field to which a DataGrid column is bound to.</param>
        /// <param name="ignoreCase">Indicates whether case-sensitive or insensitive search is performed.</param>
        /// <returns>ITemplate or null if no matching template column is found.</returns>
        public virtual ITemplate GetItemTemplate(string bindingName, bool ignoreCase)
        {
            TemplateColumn col = GetColumn(bindingName, ignoreCase) as TemplateColumn;
            return (col != null) ? col.ItemTemplate : null;
        }

        /// <summary>
        /// Gets the EditItemTemplate of the column bound to the specified field.
        /// </summary>
        /// <param name="bindingName">Data field to which a DataGrid column is bound to.</param>
        /// <returns>ITemplate or null if no matching template column is found.</returns>
        public virtual ITemplate GetEditTemplate(string bindingName)
        {
            return GetEditTemplate(bindingName, false);
        }

        /// <summary>
        /// Gets the EditItemTemplate of the column bound to the specified field.
        /// </summary>
        /// <param name="bindingName">Data field to which a DataGrid column is bound to.</param>
        /// <param name="ignoreCase">Indicates whether case-sensitive or insensitive search is performed.</param>
        /// <returns>ITemplate or null if no matching template column is found.</returns>
        public virtual ITemplate GetEditTemplate(string bindingName, bool ignoreCase)
        {
            TemplateColumn col = GetColumn(bindingName, ignoreCase) as TemplateColumn;
            return (col != null) ? col.EditItemTemplate : null;
        }

        /// <summary>
        /// Gets the index of a column bound to the specified field.
        /// </summary>
        /// <param name="bindingName">Data field to which a DataGrid column is bound to.</param>
        /// <returns>Zero based index or -1 if not found.</returns>
        public virtual int GetColumnIndex(string bindingName)
        {
            return GetColumnIndex(this, bindingName, false);
        }

        /// <summary>
        /// Gets the index of a column bound to the specified field.
        /// </summary>
        /// <param name="bindingName">Data field to which a DataGrid column is bound to.</param>
        /// <param name="ignoreCase">Indicates whether case-sensitive or insensitive search is performed.</param>
        /// <returns>Zero based index or -1 if not found.</returns>
        public virtual int GetColumnIndex(string bindingName, bool ignoreCase)
        {
            return GetColumnIndex(this, bindingName, ignoreCase);
        }

        private static int GetColumnIndex(DataGrid grid, string field, bool ignoreCase)
        {
            if (ignoreCase)
                field = field.ToLower();

            for (int idxCol = 0; idxCol < grid.Columns.Count; idxCol++)
            {
                DataGridColumn col = grid.Columns[idxCol];
                if (col is BoundColumn)
                {
                    BoundColumn boundCol = (BoundColumn)col;
                    string currField = (ignoreCase) ? boundCol.DataField.ToLower() : boundCol.DataField;
                    if (currField == field)
                        return idxCol;
                }
                else if (col is TemplateColumn)
                {
                    // Edit and item templates can be bound to different fields. Prefer edit template.
                    TemplateColumn templateCol = (TemplateColumn)col;
                    BoundTemplateBase boundTemplate;
                    bool hasBoundEditTemplate = (templateCol.EditItemTemplate != null && templateCol.EditItemTemplate is BoundTemplateBase);
                    if (hasBoundEditTemplate)
                        boundTemplate = templateCol.EditItemTemplate as BoundTemplateBase;
                    else
                        boundTemplate = templateCol.ItemTemplate as BoundTemplateBase;

                    if (boundTemplate != null)
                    {
                        string currField = (ignoreCase) ? boundTemplate.BoundField.BindingName.ToLower() : boundTemplate.BoundField.BindingName;
                        if (currField == field)
                            return idxCol;
                    }
                }
            }

            return -1;
        }

		#endregion

		#region Errors.

		/// <summary>
		/// Gets or sets the CSS Class name applied to errors.
		/// </summary>
		[Bindable(false), Category("Appearance"), DefaultValue(null),
		Description("CSS Class name applied to errors.")]
		public virtual string ErrorCss
		{
			get {return this.errorCss;}
			set {this.errorCss = value;}
		}

		/// <summary>
		/// Gets or sets the color of the error text.
		/// </summary>
		[Bindable(false), Category("Appearance"), DefaultValue(typeof(Color), "Red"),
		Description("Color of the error text.")]
        [TypeConverter(typeof(WebColorConverter))]
		public virtual Color ErrorColor
		{	
			get {return this.errorColor;}
			set {this.errorColor = value;}
		}

		/// <summary>
		/// Displays errors below the grid.
		/// </summary>
		/// <param name="brokenRules">Collection of errors to be displayed.</param>
		public void DisplayErrors(BusinessRuleCollection brokenRules)
		{
			this.errors = new string[brokenRules.Count];
			for (int errIdx=0; errIdx<this.errors.Length; errIdx++)			
				this.errors[errIdx] = brokenRules[errIdx].ToString(this.Formatter);			
		}

        /// <summary>
        /// Displays errors below the grid.
        /// </summary>
        /// <param name="multipleRowErrors">Collection of errors to be displayed.</param>
        public void DisplayErrors(BusinessRuleCollection[] multipleRowErrors)
        {
            StringCollection errorLines = new StringCollection();
            foreach (BusinessRuleCollection rowErrors in multipleRowErrors)
            {
                if (rowErrors.HasBrokenRules)
                {
                    foreach (BusinessRule brokenRule in rowErrors)
                        errorLines.Add(brokenRule.ToString(this.Formatter));
                }
            }

            this.errors = new string[errorLines.Count];
            errorLines.CopyTo(this.errors, 0);
        }

        /// <summary>
        /// Clears errors set by <b>DisplayErrors</b> methods.
        /// </summary>
        public void ClearErrors()
        {
            this.errors = new string[0];
        }

		#endregion

		#region Edit mode.

		/// <summary>
		/// Gets or sets the index of an item in the grid control to edit.
		/// </summary>
		public override int EditItemIndex
		{
			get
			{
				return base.EditItemIndex;
			}
			set
			{
				int newIndex = (value >= 0) ? value : -1;
				base.EditItemIndex = newIndex;
			}
		}

		#endregion

		#region Create item. Client-side events.

		/// <summary>
		/// Adds features to created item. Select, focus, add client-side scripts etc.
		/// </summary>
		private void HandleItemCreated(object sender, DataGridItemEventArgs e)
		{
			// Ensure minimum height for empty rows. Otherwise they cannot be clicked.
            if (e.Item.Height == default(Unit) && this.ItemStyle.Height == default(Unit) && this.MinItemHeight != default(Unit))
                e.Item.Height = this.MinItemHeight;

			// Once selected values are parsed, cache them to improve performance.
			if (this.cachedSelectedValues == null)
			{
				string[] keyValues = this.SelectedValues;
				this.cachedSelectedValues = new SelectedItemKey[keyValues.Length];
				for (int keyValIdx=0; keyValIdx<keyValues.Length; keyValIdx++)
					this.cachedSelectedValues[keyValIdx] = new SelectedItemKey(keyValues[keyValIdx]);
			}

            SetItemToolTip(e.Item);

			// Create clickable items if key value exists.
			if (this.DataKeyField != null  &&  this.DataKeyField.Length > 0  &&  this.DataSource != null  &&  e.Item.DataSetIndex >= 0  &&  e.Item.DataItem != null)
			{
				object itemId = DataBinder.Eval(e.Item.DataItem, this.DataKeyField);
				if (itemId != null)
				{
                    string currentItemId = Convert.ToString(itemId);
					CreateClickableItem(e.Item, currentItemId);

					// Reselect fields which where selected on client.
					foreach (SelectedItemKey selectedId in this.cachedSelectedValues)
					{
						if (selectedId.KeyValue == currentItemId)
						{
							// Selected item exist in data-source and it will be preserved in the selected list.
							selectedId.ItemExistsInDataSource = true;
							e.Item.ControlStyle.BackColor = this.SelectedItemStyle.BackColor;
							break;
						}
					}
				}
			}

			// Focus edited item, generate accept (Enter key) and cancel (ESC key) client-side scripts.
			if (this.Page != null  &&  e.Item.ItemType == ListItemType.EditItem)
			{
				if (e.Item.Cells.Count > 0  &&  e.Item.Cells[0].Controls.Count > 0)
				{
					// Script that focuses the control is created on its PreRender event, because it's ID 
					// is currently unknown. It will be known in PreRender stage.
					Control controlToBeFocused = e.Item.Cells[0].Controls[0];
					controlToBeFocused.PreRender += new EventHandler(ControlToBeFocused_PreRender);

					// Add client-side event handler for Enter and Escape keys.
					string onKeyDown = "";

					// Accept edit action.
					string jsAccept = (this.acceptEditActionScript != null) ? this.acceptEditActionScript.Trim() : "";
					if (jsAccept.Length > 0)
					{
						if (jsAccept.EndsWith(";") == false)
							jsAccept += ";";
						jsAccept = jsAccept.Replace("\"", "'");

						onKeyDown += "if (event.keyCode == 13){" + jsAccept + " return false;} ";
					}

					// Cancel/undo edit action.
					string jsCancel = (this.cancelEditActionScript != null) ? this.cancelEditActionScript.Trim() : "";
					if (jsCancel.Length > 0)
					{
						if (jsCancel.EndsWith(";") == false)
							jsCancel += ";";
						jsCancel = jsCancel.Replace("\"", "'");

						onKeyDown += "if (event.keyCode == 27){" + jsCancel + " return false;} ";
					}

					if (onKeyDown.Length > 0)
					{
						e.Item.Attributes["onkeydown"] = onKeyDown;
					}
				}
			}			
		}

		/// <summary>
		/// Creates a clickable item. Adds data required by client-side scripts.
		/// Method should be used in grid's ItemCreated event handler.
		/// </summary>
		/// <param name="item">DataGridItem.</param>
		/// <param name="itemId">Item ID. This is retrieved by GetSelectedIds method if the item/row is selected.</param>
		private void CreateClickableItem(DataGridItem item, string itemId)
		{
			Color hiliteColor = this.SelectedItemStyle.BackColor;
			int iRowSelectMode = (int) this.rowSelectMode;
			string selectedRowsTxtId = this.selectedValuesTxt.ClientID;

            ListItemType itemType = item.ItemType;
            if (itemType == ListItemType.Item  || itemType == ListItemType.AlternatingItem  || itemType == ListItemType.EditItem)
			{
				// Set row ID.
				item.Attributes.Add("id", this.ClientID + "_Row_" + itemId);

				// Row clicks are handled in client-side script.
				// Grid supports two modes: single and multi-row selections. Jscript handles them automatically.
                string jsRightClickHandler = (this.rightClickEnabled) ? this.RightClickAction : "";
				jsRightClickHandler = jsRightClickHandler.Replace("'", "\\'");
				jsRightClickHandler = jsRightClickHandler.Replace("\"", "\\\"");                

                string leftClickHandler = this.LeftClickAction.Replace("'", "\\'").Replace("\"", "\\\"");

                string jsOnChangeScript = (this.OnChangeAction != null) ? this.OnChangeAction : "";
                jsOnChangeScript = this.OnChangeAction.Replace("'", "\\'").Replace("\"", "\\\"");

                // Some of the data is common to all rows.
                string commonHandlerFunctionName = this.ClientID + "_OnClick";
                if (!Page.ClientScript.IsClientScriptBlockRegistered(commonHandlerFunctionName))
                {
                    string commonHandlerScript = "function " + commonHandlerFunctionName + "(evt, row, sId) {";
                    commonHandlerScript += "Biz_BoundDataGrid_RowClickedHandler(evt, row, sId, " + iRowSelectMode.ToString() + ", '" + ToHtmlColor(hiliteColor) + "', '" + selectedRowsTxtId + "', '" + leftClickHandler + "', '" + jsRightClickHandler + "', '" + jsOnChangeScript + "'); ";
                    commonHandlerScript += "} ";
                    if (!DesignMode) 
                        JScript.RegisterClientScriptBlock(commonHandlerFunctionName, commonHandlerScript, true);
                }

                string jsClickHandler = commonHandlerFunctionName + "(event, this, '" + itemId + "');";
				if (this.rightClickEnabled)
				{
					item.Attributes.Add("onmousedown", jsClickHandler);
				}
				else
				{
					item.Attributes.Add("onclick", jsClickHandler);
				}

                //string jsDoubleClickHandler = (this.doubleClickEnabled) ? this.DoubleClickAction : "";
                //if (this.doubleClickEnabled && !string.IsNullOrEmpty(jsDoubleClickHandler))
                //    item.Attributes.Add("ondblclick", jsDoubleClickHandler);

				// Change cursor to hand-style when mouse is floating over the item.
                item.Style["cursor"] = "pointer";
			}			
		}

		/// <summary>
		/// Focuses controls the published the event (sender) in PreRender stage.
		/// Sender's ID is not known before PreRender stage.
		/// </summary>
		private void ControlToBeFocused_PreRender(object sender, EventArgs e)
		{
			// Focuses the control. This can only be done on PreRender event, because ID is not known before.
			// When the page is rendered all child controls use the following pattern ':_' as defined by UniqueId.
			string idOfFocusedControl = ((Control) sender).UniqueID;

			if (idOfFocusedControl != null  &&  idOfFocusedControl.Length > 0)
			{
				// Uncomment next section if auto-scrolling to the focused control is required.
				// Set position style to 'relative'. Otherwise the 'offsetTop' property reports an invalid value.
				// This may not work with all browsers.
				//	if (sender is WebControl)
				//		((WebControl) sender).Style["position"] = "relative";
				//	else if (sender is HtmlControl)
				//		((HtmlControl) sender).Style["position"] = "relative";

				string setFocusScript = "<script language=\"JavaScript\">";
				setFocusScript += "var ctl = document.getElementById(\"" + idOfFocusedControl + "\");";
				setFocusScript += "if (ctl != null)";
				setFocusScript += "{";
				setFocusScript += "try{ctl.focus();} catch(ex){}";
				// Uncomment next line if auto-scrolling to the focused control is required.
				//	setFocusScript += "document.body.scrollTop += ctl.offsetTop;";
				setFocusScript += "}";
				setFocusScript += "</script>";

                if (!DesignMode) 
                    JScript.RegisterStartupScript(this.ID + "__Focus", setFocusScript, false);
			}
		}

		/// <summary>
		/// Convert <see cref="System.Drawing.Color"/> value to HTML hex color code.
		/// </summary>
		private string ToHtmlColor(Color color)
		{
			// Eg: Color.Blue is converted to "#0000FF".

			string red = color.R.ToString("X2");
			string green = color.G.ToString("X2");
			string blue = color.B.ToString("X2");
			string htmColor = "#" + red + green + blue;

			return htmColor;			
		}

		#endregion

		#region Render.

		/// <summary>
		/// Sends server control content to a provided <see cref="HtmlTextWriter"/> object, which writes the content to be rendered on the client.
		/// </summary>
		/// <param name="output">The <b>HtmlTextWriter</b> object that receives the server control content.</param>
		protected override void Render(HtmlTextWriter output)
		{
			// First emit all required scripts.            
			EmitJavaScript(output);
            output.WriteLine();
            if (this.DoubleClickEnabled && !string.IsNullOrEmpty(this.DoubleClickAction))
                this.Attributes["ondblclick"] = this.DoubleClickAction;
            if (UsesHotKeys())
                this.Attributes["onkeydown"] = CallHotKeyEventHandler();

			base.Render(output);

			// Fill hidden textbox with keys of selected items.
			if (this.cachedSelectedValues != null  &&  this.selectedValuesTxt.Text.Trim().Length > 0)
			{
				// Remove selected values that don't exist in the data-source any more.
				string newSelectedData = "";

				string oldSelectedData = this.selectedValuesTxt.Text.Trim();
				// Remove last row delimiter (";").				
				if (oldSelectedData.EndsWith(RowDelimiter))
					oldSelectedData = oldSelectedData.Substring(0, oldSelectedData.Length-1);

				string[] rowData = oldSelectedData.Split(RowDelimiter.ToCharArray());			
				string[] ids = new string[rowData.Length];
				string[] attributes = new string[rowData.Length];

				for (int rowIdx=0; rowIdx<ids.Length; rowIdx++)			
				{
					// ID is the first attribute.
					ids[rowIdx] = rowData[rowIdx].Substring(0, rowData[rowIdx].IndexOf(RowAttributesDelimiter));
					// The rest or the current row data is copied to attributes.
					if (rowData[rowIdx].Length > ids[rowIdx].Length)
						attributes[rowIdx] = rowData[rowIdx].Substring(ids[rowIdx].Length);
					else
						attributes[rowIdx] = "";

					// Check if current id exists in the data-cource and add it to the new list if it does.
					foreach (SelectedItemKey itemKey in this.cachedSelectedValues)
					{
						if (itemKey.KeyValue == ids[rowIdx]  &&  itemKey.ItemExistsInDataSource)
						{
							newSelectedData += ids[rowIdx] + attributes[rowIdx] + RowDelimiter;
							break;
						}
					}
				}
				
				this.selectedValuesTxt.Text = newSelectedData;
			}
			this.selectedValuesTxt.RenderControl(output);

			// Display errors.
			if (this.errors != null)
			{
				foreach (string error in this.errors)
				{
					output.Write("<br/>");
					Label errorLbl = new Label();
					errorLbl.EnableViewState = false;
					errorLbl.Text = error;
					errorLbl.ForeColor = this.ErrorColor;
					if (this.ErrorCss != null  &&  this.ErrorCss.Trim() != "")
						errorLbl.CssClass = this.ErrorCss;

					errorLbl.RenderControl(output);
				}
			}
		}

        private string RenderJavaScript()
        {
            StringBuilder script = new StringBuilder();
            script.Append("<script type=\"text/javascript\" language=\"javascript\">");
            script.Append("function " + CreateGetSelectedKeyValueJScriptName() + "()");
            script.Append("{");
            script.Append("return Biz_BoundDataGrid_GetSelectedValue(\"" + this.selectedValuesTxt.ClientID + "\"); ");
            script.Append("} ");
            script.Append("</script>");
            return script.ToString();
        }

		private void EmitJavaScript(HtmlTextWriter output)
		{           
            output.Write(RenderJavaScript());
		}

		#endregion

        #region BoundColumnStyles.

        /// <summary>
        /// CSS inline style attributes for bound columns. <b>Never</b> access this member directly.
        /// Always access it through <b>BoundColumnStyles</b> property which ensures that it has ben properly initialized.
        /// </summary>
        BoundColumnStyleCollection boundColumnStyles;

        /// <summary>
        /// Gets a collection of all HTML cascading-style sheets (CSS) inline style attributes for bound <b>DataGrid</b> columns.
        /// </summary>
        [Category("Style"),
        Description("HTML cascading-style sheets (CSS) inline style attributes for bound DataGrid columns. Currently, they can only be set programmatically.")]
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public BoundColumnStyleCollection BoundColumnStyles
        {
            get 
            { 
                if (this.boundColumnStyles == null)
                    this.boundColumnStyles = new BoundColumnStyleCollection(this);

                return this.boundColumnStyles; 
            }
        }

        #endregion

        #region BoundColumnAttributes.

        /// <summary>
        /// HTML attributes for bound columns. <b>Never</b> access this member directly.
        /// Always access it through <b>BoundColumnAttributes</b> property which ensures that it has ben properly initialized.
        /// </summary>
        BoundColumnAttributesCollection boundColumnAttributes;

        /// <summary>
        /// Gets a collection of all HTML attributes for bound <b>DataGrid</b> columns.
        /// </summary>
        [Category("Style"),
        Description("HTML attributes for bound DataGrid columns. Currently, they can only be set programmatically.")]
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public BoundColumnAttributesCollection BoundColumnAttributes
        {
            get
            {
                if (this.boundColumnAttributes == null)
                    this.boundColumnAttributes = new BoundColumnAttributesCollection(this);

                return this.boundColumnAttributes;
            }
        }

        #endregion

        #region ToolTips.

        /// <summary>
        /// Gets or sets the format which specifies how item/row tooltip is constructed from bound data.
        /// </summary>
        /// <remarks>The specified value must obey the same format as the format used by
        /// <see cref="string.Format(string, object[])"/> method.</remarks>
        [Category("Behavior")]
        [Description("Specifies how item/row tooltip is constructed from bound data.")]
        [DefaultValue(null)]
        public virtual string ItemToolTipFormat
        {
            get { return itemToolTipFormat; }
            set { itemToolTipFormat = value; }
        }

        /// <summary>
        /// Gets or sets semicolon delimited fields of the data source that provide the values
        /// for item/row tooltips as defined in <see cref="ItemToolTipFormat"/> property.
        /// </summary>
        [Category("Behavior")]
        [Description("Semicolon delimited fields of the data source that provide the values for item/row tooltips.")]
        [DefaultValue(null)]
        public virtual string ItemToolTipFields
        {
            get { return itemToolTipFields; }
            set { itemToolTipFields = value; }
        }

        /// <summary>
        /// Gets or sets a collection of formats which specify how field/column tooltips are constructed from bound data.
        /// </summary>
        /// <remarks>The specified format values must obey the same format as the format used by
        /// <see cref="string.Format(string, object[])"/> method.</remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IDictionary<string, string> FieldToolTipFormats
        {
            get { return this.fieldToolTipFormats; }
            set { this.fieldToolTipFormats = (value != null) ? value : new Dictionary<string, string>(); }
        }

        /// <summary>
        /// Gets or sets a collection of tooltip arguments, ie. semicolon delimited fields of the data source, that 
        /// provide the values for field/column tooltips as defined in <see cref="FieldToolTipFormats"/> collection.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IDictionary<string, string> FieldToolTipArguments
        {
            get { return this.fieldToolTipArguments; }
            set { this.fieldToolTipArguments = (value != null) ? value : new Dictionary<string, string>(); }
        }        

        /// <summary>
        /// Gets or sets whether the grid should automatically generate cell tooltips for all bound fields.
        /// </summary>
        [Category("Behavior")]
        [Description("Whether the grid should automatically generate cell tooltips for all bound fields.")]
        [DefaultValue(false)]
        public bool FieldToolTipsAutoGenerated
        {
            get { return this.fieldToolTipsAutoGenerated; }
            set { this.fieldToolTipsAutoGenerated = value; }
        }

        bool dontSetRowToolTips = false;
        StringCollection fieldsWithBadToolTipFormat = new StringCollection();

        private void SetItemToolTip(DataGridItem item)
        {
            bool alreadyHasCustomToolTip = !string.IsNullOrEmpty(item.ToolTip);
            if (alreadyHasCustomToolTip)
                return;

            bool rowAlgorithmDefined = !string.IsNullOrEmpty(this.ItemToolTipFormat) && !string.IsNullOrEmpty(this.ItemToolTipFields);
            bool cellAlgorithmsDefined = (this.FieldToolTipFormats.Count > 0) && (this.FieldToolTipArguments.Count > 0);
            bool nothingToDo = (!rowAlgorithmDefined && !cellAlgorithmsDefined);
            if (nothingToDo)
                return;

            bool isReadOnlyDataItem = (item.ItemType == ListItemType.Item || item.ItemType == ListItemType.AlternatingItem);
            if (!isReadOnlyDataItem)
                return;

            if (!dontSetRowToolTips)
            {
                bool noErrors = TrySetRowToolTip(item, this.ItemToolTipFormat, this.ItemToolTipFields);
                if (!noErrors)
                    this.dontSetRowToolTips = true;
            }

            foreach (KeyValuePair<string, string> fieldAndFormat in this.FieldToolTipFormats)
            {
                string field = fieldAndFormat.Key;
                bool hasErrors = this.fieldsWithBadToolTipFormat.Contains(field);
                if (hasErrors)
                    continue;

                string format = fieldAndFormat.Value;
                string arguments = null;
                this.FieldToolTipArguments.TryGetValue(field, out arguments);
                bool isAlgorithmDefined = !string.IsNullOrEmpty(format) && !string.IsNullOrEmpty(arguments);
                if (isAlgorithmDefined)
                {
                    bool noErrors = TrySetCellToolTip(item, field, format, arguments);
                    if (!noErrors)
                        this.fieldsWithBadToolTipFormat.Add(field);
                }
            }
        }

        private bool TrySetRowToolTip(DataGridItem item, string format, string fieldsContainingFormatArguments)
        {
            if (string.IsNullOrEmpty(fieldsContainingFormatArguments))
                return false;

            bool isSucces;
            try
            {
                string[] fields = fieldsContainingFormatArguments.Split(';');
                List<object> arguments = new List<object>();
                foreach (string field in fields)
                {
                    if (!string.IsNullOrEmpty(field))
                        arguments.Add(DataBinder.Eval(item.DataItem, field));
                }

                item.ToolTip = string.Format(format, arguments.ToArray());
                isSucces = true;
            }
            catch
            {
                isSucces = false;
            }

            return isSucces;
        }

        private bool TrySetCellToolTip(DataGridItem item, string fieldBoundToCell, string format, string fieldsContainingFormatArguments)
        {
            int idxField = GetColumnIndex(this, fieldBoundToCell, false);
            if (idxField < 0)
                return false;

            if (idxField >= item.Cells.Count)
                return false;

            try
            {
                string[] fields = fieldsContainingFormatArguments.Split(';');
                List<object> arguments = new List<object>();
                foreach (string field in fields)
                {
                    if (!string.IsNullOrEmpty(field))
                    {
                        object arg = DataBinder.Eval(item.DataItem, field);
                        if (this.Formatter != null)
                            arg = this.Formatter.Format(arg);

                        arguments.Add(arg);
                    }
                }

                item.Cells[idxField].ToolTip = string.Format(format, arguments.ToArray());
                return true;
            }
            catch
            {
                return false;
            }
        }        

        private void AutoGenerateFieldToolTips()
        {
            foreach (string fieldName in GetAllBoundFieldNames())
            {
                this.FieldToolTipArguments[fieldName] = fieldName;
                this.FieldToolTipFormats[fieldName] = "{0}";
            }
        }

        private List<string> GetAllBoundFieldNames()
        {
            List<string> fieldNames = new List<string>();
            foreach (DataGridColumn col in this.Columns)
            {
                if (col is BoundColumn)
                {
                    BoundColumn boundCol = (BoundColumn)col;
                    fieldNames.Add(boundCol.DataField);
                }
                else if (col is TemplateColumn)
                {
                    BoundTemplateBase boundTemplate = GetBoundTemplateForColumn((TemplateColumn)col);
                    if ((boundTemplate != null) && (boundTemplate.BoundField != null))
                        fieldNames.Add(boundTemplate.BoundField.BindingName);
                }
            }

            return fieldNames;
        }

        private BoundTemplateBase GetBoundTemplateForColumn(TemplateColumn templateCol)
        {
            BoundTemplateBase boundTemplate;
            bool hasBoundEditTemplate = (templateCol.EditItemTemplate != null && templateCol.EditItemTemplate is BoundTemplateBase);
            if (hasBoundEditTemplate)
                boundTemplate = templateCol.EditItemTemplate as BoundTemplateBase;
            else
                boundTemplate = templateCol.ItemTemplate as BoundTemplateBase;

            return boundTemplate;
        }

        #endregion

        #region FocusItem.

        /// <summary>
        /// Sets input focus to item at the specified index.
        /// </summary>
        /// <param name="itemIndex">Zero-based item index.</param>        
        /// <remarks><para>Does nothing if index is out of range or if no columns have been defined.</para>
        /// <para>The method tries to find an editable control in the item row and focuses it. If it cannot determine
        /// which is the first editable control, then the first possible control is focused.</para></remarks>
        public virtual void FocusItem(int itemIndex)
        {
            FocusItem(itemIndex, false);
        }

        /// <summary>
        /// Sets input focus to item at the specified index.
        /// </summary>
        /// <param name="itemIndex">Zero-based item index.</param>
        /// <param name="changeSelectedIndex">Indicates whether <see cref="SelectedIndex"/> property should be changed to the specified value.</param>
        /// <remarks><para>Does nothing if index is out of range or if no columns have been defined.</para>
        /// <para>The method tries to find an editable control in the item row and focuses it. If it cannot determine
        /// which is the first editable control, then the first possible control is focused.</para></remarks>
        public virtual void FocusItem(int itemIndex, bool changeSelectedIndex)
        {
            if (DesignMode)
                return;
            if (this.Columns.Count == 0)
                return;
            bool isValidIndex = (itemIndex >= 0) && (itemIndex < this.Items.Count);
            if (!isValidIndex)
                return;

            if (changeSelectedIndex)
                this.SelectedIndex = itemIndex;

            DataGridItem item = this.Items[itemIndex];
            int idxOfCellWithEditableControl, idxOfEditableControl;
            bool foundEditableCtl = FindEditableControl(item, out idxOfCellWithEditableControl, out idxOfEditableControl);
            if (foundEditableCtl)
            {
                Control ctl = item.Cells[idxOfCellWithEditableControl].Controls[idxOfEditableControl];
                JScript.Focus(ctl);
            }
            else
            {                
                Control ctl = GetFirstControl(item);
                if (ctl != null && !(ctl is LiteralControl))
                {
                    // Literal controls will cannot be focused since they are rendered as TD content.
                    JScript.Focus(ctl);
                }
                else
                {
                    // The following works only in IE.
                    string rowId = item.Attributes["id"];
                    if (!string.IsNullOrEmpty(rowId))
                    {
                        // A small delay (200ms) is required to, presumably, ensure that the document is fully loaded.
                        string jsFocusCell = string.Format("Biz_BoundDataGrid_FocusRowWithDelay('{0}', {1});", rowId, 200);
                        JScript.RegisterStartupScript(jsFocusCell, true);
                    }
                }
            }
        }

        private bool FindEditableControl(DataGridItem item, out int idxOfCellWithEditableControl, out int idxOfEditableControl)
        {
            idxOfCellWithEditableControl = -1;
            idxOfEditableControl = -1;
            for (int idxCell = 0; idxCell < this.Columns.Count; idxCell++)
            {
                TableCell cell = item.Cells[idxCell];
                for (int idxControl = 0; idxControl < cell.Controls.Count; idxControl++)
                {
                    Control ctl = cell.Controls[idxControl];
                    if (IsEditControl(ctl))
                    {
                        idxOfCellWithEditableControl = idxCell;
                        idxOfEditableControl = idxControl;
                        return true;
                    }
                }
            }

            return false;
        }

        private Control GetFirstControl(DataGridItem item)
        {
            foreach (TableCell cell in item.Cells)
            {
                foreach (Control ctl in cell.Controls)
                    return ctl;
            }

            return null;
        }

        private static bool IsEditControl(Control ctl)
        {
            if (ctl is IPostBackDataHandler)
                return true;
            else if (ctl is IBoundControl)
                return true;
            else
                return false;
        }

        #endregion
    }
}